package com.spring.mvc.queue;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class LinkListQueue<T> {
	
	private final AtomicInteger count=new AtomicInteger(0);
	private final ReentrantLock takeLock=new ReentrantLock();
	private final Condition notEmpty;
	private final ReentrantLock putLock;
	
	private Node<T> head;
	private Node<T> last;
	
	public LinkListQueue() {
		notEmpty=takeLock.newCondition();
		putLock=new ReentrantLock();
		
		this.head=new Node<T>(null);
		this.last=this.head;
	}
	
	public int size(){
		return this.count.get();
	}
	
	public boolean offer(T t){
		if(null == t)throw new NullPointerException();
		
		int c=-1;
		ReentrantLock putLock=this.putLock;
		AtomicInteger count=this.count;
		putLock.lock();
		try{
			insert(t);
			c=count.getAndIncrement();
		}finally{
			putLock.unlock();
		}
		if(c==0){
			signalNotEmpty();
		}
		return c>=0;
	}
	
	public T take() throws InterruptedException{
		T t=null;
		int c=-1;
		ReentrantLock takeLock=this.takeLock;
		AtomicInteger count=this.count;
		takeLock.lockInterruptibly();
		try{
			try {
				while(count.get()==0){
					this.notEmpty.await();
				}
			} catch (InterruptedException e) {
				signalNotEmpty();
				throw e;
			}
			
			t=extract();
			c=count.getAndDecrement();
			if(c>1){
				signalNotEmpty();
			}
		}finally{
			takeLock.unlock();
		}
		return t;
	}
	
	public void clear(){
		fullyLock();
		try{
			this.head.next=null;
			this.last=this.head;
		}finally{
			fullyUnlock();
		}
	}

	private void fullyLock() {
		this.takeLock.lock();
		this.putLock.lock();
	}
	
	private void fullyUnlock(){
		this.takeLock.unlock();
		this.putLock.unlock();
	}

	private T extract() {
		Node<T> first=this.head.next;
		this.head=null;
		this.head=first;
		T t=first.item;
		first.item=null;
		this.head.item=null;
		first=null;
		return t;
	}

	private void signalNotEmpty() {
		ReentrantLock takeLock=this.takeLock;
		takeLock.lock();
		try{
			this.notEmpty.signal();
		}finally{
			takeLock.unlock();
		}
	}

	private void insert(T t) {
		this.last.next=new Node<T>(t);
		this.last=this.last.next;
	}

	static class Node<T>{
		volatile T item;
		Node<T> next;
		public Node(T t) {
			this.item=t;
		}
	}
}
